//
//  sdl_queue.c
//  FFmpegDemo
//
//  Created by xiaerfei on 2019/5/9.
//  Copyright © 2019 erfeixia. All rights reserved.
//

#include "sdl_queue.h"

#pragma mark - packet queue

/// 是一个特殊的packet，主要用来作为非连续的两端数据的“分界”标记。
AVPacket flush_pkt;
int packet_queue_put_private(PacketQueue *q, AVPacket *pkt);

/* packet queue handling */
int packet_queue_init(PacketQueue *q) {
    // 清零
    memset(q, 0, sizeof(PacketQueue));
    // 创建锁
    q->mutex = SDL_CreateMutex();
    if (!q->mutex) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    // 创建信号量
    q->cond = SDL_CreateCond();
    if (!q->cond) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    q->pkt_num_cond = SDL_CreateCond();
    if (!q->pkt_num_cond) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    // 这里设置为 1 表示还未启用
    q->abort_request = 1;
    return 0;
}

void packet_queue_destroy(PacketQueue *q) {
    // 销毁过程负责清理mutex和cond
    // 先清除所有的节点
    packet_queue_flush(q);
    // 销毁锁
    SDL_DestroyMutex(q->mutex);
    // 销毁信号量
    SDL_DestroyCond(q->cond);
    SDL_DestroyCond(q->pkt_num_cond);
}

void packet_queue_start(PacketQueue *q) {
    SDL_LockMutex(q->mutex);
    q->abort_request = 0;
    // 这里放入了一个flush_pkt
    packet_queue_put_private(q, &flush_pkt);
    SDL_UnlockMutex(q->mutex);
}

void packet_queue_abort(PacketQueue *q) {
    SDL_LockMutex(q->mutex);
    q->abort_request = 1;
    SDL_CondSignal(q->cond);
    SDL_UnlockMutex(q->mutex);
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
    int ret;
    
    SDL_LockMutex(q->mutex);
//    if (q->nb_packets > 30000) {
//        // wait
//        SDL_CondWait(q->pkt_num_cond, q->mutex);
//    }
    ret = packet_queue_put_private(q, pkt);
    SDL_UnlockMutex(q->mutex);
    
    if (pkt != &flush_pkt && ret < 0) {
        // 放入失败，释放AVPacket
        av_packet_unref(pkt);
    }
    
    return ret;
}
/*
 主要完成3件事：
 
 1.计算serial。serial标记了这个节点内的数据是何时的。
   一般情况下新增节点与上一个节点的serial是一样的，但
   当队列中加入一个flush_pkt后，后续节点的serial会比之前大1.
 2.队列操作。经典的队列实现方式，不展开了。
 3.队列属性操作。更新队列中节点的数目、占用字节数（含AVPacket.data的大小）及其时长。
 */
int packet_queue_put_private(PacketQueue *q, AVPacket *pkt) {
    MyAVPacketList *pkt1;
    // 如果已中止，则返回失败
    if (q->abort_request) { return -1; }
    // 分配节点内存
    pkt1 = av_malloc(sizeof(MyAVPacketList));
    // 内存不足，则放入失败
    if (!pkt1) { return -1; }
    // 拷贝AVPacket(浅拷贝，AVPacket.data等内存并没有拷贝)
    pkt1->pkt = *pkt;
    pkt1->next = NULL;
    // 如果放入的是flush_pkt，需要增加队列的序列号，以区分不连续的两段数据
    if (pkt == &flush_pkt) { q->serial++; }
    // 用队列序列号标记节点
    pkt1->serial = q->serial;
    if (!q->last_pkt) {
        // 队列操作：如果last_pkt为空，说明队列是空的，新增节点为队头；
        q->first_pkt = pkt1;
    } else {
        // 否则，队列有数据，则让原队尾的next为新增节点。 最后将队尾指向新增节点
        q->last_pkt->next = pkt1;
    }
    q->last_pkt = pkt1;
    // 队列属性操作：增加节点数、cache大小、cache总时长
    q->nb_packets++;
    q->size += pkt1->pkt.size + sizeof(*pkt1);
    q->duration += pkt1->pkt.duration;
    /* XXX: should duplicate packet data in DV case */
    // 发出信号，表明当前队列中有数据了，通知等待中的读线程可以取数据了
    SDL_CondSignal(q->cond);
    return 0;
}

/* return < 0 if aborted, 0 if no packet and > 0 if packet.  */
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial) {
    MyAVPacketList *pkt1;
    int ret;
    
    SDL_LockMutex(q->mutex);
    
    for (;;) {
        if (q->abort_request) {
            ret = -1;
            break;
        }
        // MyAVPacketList *pkt1; 从队头拿数据
        pkt1 = q->first_pkt;
        if (pkt1) {// 队列中有数据
            // 队头移到第二个节点
            q->first_pkt = pkt1->next;
            // 如果队头为空，那么把队尾也置为空
            if (!q->first_pkt) { q->last_pkt = NULL; }
            // 节点数减1
            q->nb_packets--;
            // cache大小减去一个节点
            q->size -= pkt1->pkt.size + sizeof(*pkt1);
            // 总时长减去一个节点
            q->duration -= pkt1->pkt.duration;
            // 返回AVPacket，这里发生一次AVPacket结构体拷贝，AVPacket的data只拷贝了指针
            *pkt = pkt1->pkt;
            if (serial) {
                // 如果需要输出serial，把serial输出
                *serial = pkt1->serial;
            }
            // 释放节点内存
            av_free(pkt1);
            ret = 1;
            break;
        } else if (!block) {
            // 队列中没有数据，且非阻塞调用
            ret = 0;
            break;
        } else {
            // 队列中没有数据，且阻塞调用
            // 这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
            SDL_CondWait(q->cond, q->mutex);
        }
    }
//    if (q->nb_packets < 10000) {
//        SDL_CondSignal(q->pkt_num_cond);
//    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}


void packet_queue_flush(PacketQueue *q) {
    MyAVPacketList *pkt, *pkt1;
    
    SDL_LockMutex(q->mutex);
    for (pkt = q->first_pkt; pkt; pkt = pkt1) {
        pkt1 = pkt->next;
        av_packet_unref(&pkt->pkt);
        av_freep(&pkt);
    }
    q->last_pkt = NULL;
    q->first_pkt = NULL;
    q->nb_packets = 0;
    q->size = 0;
    q->duration = 0;
    SDL_UnlockMutex(q->mutex);
}

int packet_queue_put_nullpacket(PacketQueue *q, int stream_index) {
    AVPacket pkt1, *pkt = &pkt1;
    av_init_packet(pkt);
    pkt->data = NULL;
    pkt->size = 0;
    pkt->stream_index = stream_index;
    return packet_queue_put(q, pkt);
}

#pragma mark - frame queue
/*
 初始化函数主要是内存初始化、锁初始化。调用参数中两个比较重要的是max_size和keep_last。
 其中max_size是最大允许存储的节点个数，但最大不超过FRAME_QUEUE_SIZE 16
 */
int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last) {
    int i;
    memset(f, 0, sizeof(FrameQueue));
    if (!(f->mutex = SDL_CreateMutex())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    if (!(f->cond = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    f->pktq = pktq;
    f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);
    // 双感叹号是C中的一种技巧，旨在让int参数规整为0/1的“bool值”
    f->keep_last = !!keep_last;
    // 数组queue中的每个元素的frame(AVFrame*)的字段调用av_frame_alloc分配内存
    for (i = 0; i < f->max_size; i++)
        if (!(f->queue[i].frame = av_frame_alloc()))
            return AVERROR(ENOMEM);
    return 0;
}
/*
 frame_queue_unref_item释放的内存都是关联的内存，而非结构体自身内存。
 AVFrame内部有许多的AVBufferRef类型字段，而AVBufferRef只是AVBuffer的引用，
 AVBuffer通过引用计数自动管理内存（简易垃圾回收机制）。因此AVFrame在不需要的时
 候，需要通过av_frame_unref减少引用计数。
 
 关于AVBufferRef的内存管理机制，可以参考这篇文章：
 https://blog.csdn.net/muyuyuzhong/article/details/79381152
 */
void frame_queue_unref_item(Frame *vp) {
    av_frame_unref(vp->frame);// frame计数减1
    avsubtitle_free(&vp->sub);// sub关联的内存释放
}
/*
 较为重要的是queue元素的释放。分两步，分别是frame_queue_unref_item和av_frame_free。
 其中av_frame_free与初始化中的av_frame_alloc对应，用于释放AVFrame.
 */
void frame_queue_destory(FrameQueue *f) {
    int i;
    for (i = 0; i < f->max_size; i++) {
        Frame *vp = &f->queue[i];
        frame_queue_unref_item(vp);
        av_frame_free(&vp->frame);
    }
    SDL_DestroyMutex(f->mutex);
    SDL_DestroyCond(f->cond);
}

/*
 FrameQueue的“写”分两步，先调用frame_queue_peek_writable获取一个可写节点，
 在对节点操作结束后，调用frame_queue_push告知FrameQueue“存入”该节点。
 
 阅读提示：
 在ffplay中，FrameQueue始终是一个线程写，另一个线程读。也就是只有一个读线程，
 不会有其他读线程竞争读；只有一个写线程，不会有其他线程竞争写；唯一需要的是读与
 写线程间的同步。FrameQueue的整个优化和设计思路正是基于这一点的。
 
 整个函数分3步：
 
 1.加锁情况下，等待直到队列有空余空间可写（f->size < f->max_size）
 2.如果有退出请求（f->pktq->abort_request），则返回NULL
 3.返回windex位置的元素（windex指向当前应写位置）
 
 为什么这里锁的范围不是整个函数呢？这是为了减小锁的范围，以提高效率。而之所以可以
 在无锁的情况下安全访问queue 字段，是因为上文中提到的单读单写的特殊场景。首先，
 queue是一个预先分配好的数组，因此queue本身不发生变化，可以安全访问；接着queue
 内的元素，读和写不存在重叠，即windex和rindex不会重叠。
 
 关于“读和写不存在重叠”，仔细看看。因为queue数组被当做一个环形缓冲区使用，那么的
 确存在underrun和overrun的情况，即读过快，或写过快的情况，这时如果不加控制，就
 会呈现缓冲区覆盖。
 
 FrameQueue的精明之处在于，先通过size判断当前缓冲区内空间是否够写，或者够读，比
 如这里先通过一个循环的条件等待，判断f->size >= f->max_size，如果f->size >= f->max_size，
 那么说明队列中的节点已经写满，也就是已经overrun了，此时如果再写，肯定会覆写未读数
 据，那么就需要继续等待。当无需等待时，windex指向的内存一定是已经读过的（除非代码异常了）。
 
 调用frame_queue_peek_writable取到Frame指针后，就可以对Frame内的字段自由改写，
 因为只有一个写进程，且无需担心读进程覆写（如上分析，读进程要读一个节点时，也会先判断
 underrun的情况）。
 
 Frame* vp = frame_queue_peek_writable(q);
 //将要存储的数据写入frame字段，比如：
 av_frame_move_ref(vp->frame, src_frame);
 //存入队列
 frame_queue_push(q);
 
 */
Frame *frame_queue_peek_writable(FrameQueue *f) {
    /* wait until we have space to put a new frame */
    SDL_LockMutex(f->mutex);
    while (f->size >= f->max_size &&
           !f->pktq->abort_request) {
        SDL_CondWait(f->cond, f->mutex);
    }
    SDL_UnlockMutex(f->mutex);
    
    if (f->pktq->abort_request)
        return NULL;
    
    return &f->queue[f->windex];
}
/*
 frame_queue_push函数，执行两个步骤：
 
 1.windex加1，如果超过max_size，则回环为0
 2.加锁情况下大小加1.

 因为FrameQueue是基于固定长度的数组实现的队列，与链表队列不同，其节点在初始化的时候已
 经在队列中了，push所要做的只是通过某种标志记录该节点是否是写入未读的。ffplay的做法是
 对windex加1，将写指针移动到下一个元素，凡是windex“之前”的节点，都是写过的。（至于是
 否可读，rindex知道；至于后续有多少空间可写，size知道）
 
 */
void frame_queue_push(FrameQueue *f) {
    if (++f->windex == f->max_size)
        f->windex = 0;
    SDL_LockMutex(f->mutex);
    f->size++;
    SDL_CondSignal(f->cond);
    SDL_UnlockMutex(f->mutex);
}
/*
 和写一样，FrameQueue的读也分两步。frame_queue_peek_readable和frame_queue_next。
 相比写要复杂一点的是 ，读的代码多考虑另一个特性，即允许保留上一读节点。
 
 Frame* vp = frame_queue_peek_readable(f);
 //读取vp的数据，比如
 printf("pict_type=%d\n", vp->frame->pict_type);
 frame_queue_next(f);

 
 */

Frame *frame_queue_peek_readable(FrameQueue *f) {
    /* wait until we have a readable a new frame */
    SDL_LockMutex(f->mutex);
    while (f->size - f->rindex_shown <= 0 &&
           !f->pktq->abort_request) {
        SDL_CondWait(f->cond, f->mutex);
    }
    SDL_UnlockMutex(f->mutex);
    
    if (f->pktq->abort_request)
        return NULL;
    
    return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}

void frame_queue_next(FrameQueue *f) {
    if (f->keep_last && !f->rindex_shown) {
        f->rindex_shown = 1;
        return;
    }
    frame_queue_unref_item(&f->queue[f->rindex]);
    if (++f->rindex == f->max_size)
        f->rindex = 0;
    SDL_LockMutex(f->mutex);
    f->size--;
    SDL_CondSignal(f->cond);
    SDL_UnlockMutex(f->mutex);
}
//读当前节点（上文中的用词是“将要读的节点”，也就是黑色块），与frame_queue_peek_readable等效，但没有检查是否有可读节点
Frame *frame_queue_peek(FrameQueue *f) {
    return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
}
//读下一个节点
Frame *frame_queue_peek_next(FrameQueue *f) {
    return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size];
}
//读上一个节点
Frame *frame_queue_peek_last(FrameQueue *f) {
    return &f->queue[f->rindex];
}
  
/* return the number of undisplayed frames in the queue */
int frame_queue_nb_remaining(FrameQueue *f) {
    return f->size - f->rindex_shown;
}

/* return last shown position */
int64_t frame_queue_last_pos(FrameQueue *f) {
    Frame *fp = &f->queue[f->rindex];
    if (f->rindex_shown && fp->serial == f->pktq->serial)
        return fp->pos;
    else
        return -1;
}
